/**
 * 
 */
package uk.ac.ebi.velvet.util.loader;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;

import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import uk.ac.ebi.curtain.utils.data.FileType;
import uk.ac.ebi.curtain.utils.data.IdentifierProcessor;
import uk.ac.ebi.curtain.utils.data.ReadType;
import uk.ac.ebi.curtain.utils.data.impl.SolexaIdentifierProcessor;
import uk.ac.ebi.curtain.utils.file.EntryMapper;
import uk.ac.ebi.curtain.utils.file.EntryMapperBean;
import uk.ac.ebi.curtain.utils.file.FileInfo;
import uk.ac.ebi.curtain.utils.io.MultipleFileHandler;
import uk.ac.ebi.curtain.utils.io.MultipleFileHandler.FileAccess;
import uk.ac.ebi.curtain.utils.io.impl.FileIO;
import uk.ac.ebi.velvet.config.VelvetDataAccessService;
import uk.ac.ebi.velvet.model.ReadData;
import uk.ac.ebi.velvet.model.Strand;
import uk.ac.ebi.velvet.model.impl.ReadDataImpl;
import uk.ac.ebi.velvet.util.file.GlobalRandomFileAccess;
import uk.ac.ebi.velvet.util.io.SequenceUtil;
import uk.ac.ebi.velvet.util.io.WriteException;

/**
 * @author mhaimel
 *
 */
public class ShortReadPairLoader implements Callable<ReadData[]> {
	
	private enum Category{UnmappedRead,UnmappedPaired,MappingId};
	
	private GlobalRandomFileAccess globalAccess = null;
	private Log log = LogFactory.getLog(this.getClass());
	private final IdentifierProcessor idProc;
	private VelvetDataAccessService dataAccess;
	private ReadData[] readArr = null;
	private int readCnt;
	private List<FileInfo> readFiles = new ArrayList<FileInfo>();
//	private PrintWriter outMapping = null;
//	private PrintWriter outFasta;
//	private File unmappedReadFile;
//	private File unmappedPairedReadFile;
//	private File mappingFile;

	private MultipleFileHandler<PrintWriter, Category> fHandler = 
		new MultipleFileHandler<PrintWriter, Category>(new FileAccess<PrintWriter>(){
			@Override
			public PrintWriter openFile(File file) throws IOException {
				return new FileIO(file,false).getPrintWriter();
			}});
	
	public ShortReadPairLoader(VelvetDataAccessService data, int readCnt) {
		idProc = new SolexaIdentifierProcessor();
		this.dataAccess = data;
		this.readCnt = readCnt;
		globalAccess = new GlobalRandomFileAccess(data);
		this.readArr = new ReadData[this.readCnt];
	}
	
	public void setUnmappedReadFile(File file){
		getFileHandler().addFile(file, Category.UnmappedRead);
	}

	public void setUnmappedPairedReadFile(File file){
		getFileHandler().addFile(file, Category.UnmappedPaired);
	}
	
	public void setMappingFile(File file){
		getFileHandler().addFile(file, Category.MappingId);
	}	
	
	public PrintWriter getOutput(Category id) {
		try {
			return fHandler.getChannel(id);
		} catch (IOException e) {
			throw new WriteException(e);
		}
	}
	
	public void addReadFiles(Collection<FileInfo> infoColl){
		readFiles.addAll(infoColl);
	}


	@Override
	public ReadData[] call() throws Exception {
		return loadAll();
	}
	
	public ReadData[] loadAll(){
		return load(readFiles);
	}

	private ReadData[] load(Iterable<FileInfo> infoColl) throws WriteException{
		try {
			getFileHandler().openAll();
			DataMapper dataMapper = new DataMapper();
			for(FileInfo info : infoColl){
				getLog().info("Process File " + info);
				getGlobFileAccess().getGlobalStartPosition(info.getFile());
				info.getFileType().getParser().parse(getGlobFileAccess().getChannel(info.getFile()), info, dataMapper);
				getGlobFileAccess().close(info.getFile());
				getLog().debug("Finished processing file " + info);
			}		
		} catch (IOException e) {
			throw new WriteException(e);
		} finally{
			getFileHandler().closeAll();
			getGlobFileAccess().closeAll();
		}
		getLog().debug("Finished loading reads!");
		return this.readArr;
	}
	
	

	private GlobalRandomFileAccess getGlobFileAccess() {
		return globalAccess;
	}
		
	public void clear(){
		this.readArr = null;
	}

	private MultipleFileHandler<PrintWriter, Category> getFileHandler() {
		return fHandler;
	}
	
	private String removeStrandInformation(String id){
		return idProc.removeStrandInformation(id);
	}

	public String cleanId(String id) {
		return idProc.cleanId(id);
	}

	public Strand getStrand(String id) {
		return idProc.getStrand(id);
	}
	
	private void addEntry(EntryMapperBean bean){
		String currIdStr = cleanId(bean.getId());
		if(bean.getInfo().getReadType().equals(ReadType.ShortPaired)){
			currIdStr = removeStrandInformation(bean.getId());
		}
		Integer idx = getIdx(currIdStr); // only once needed
		if(null != idx){
			getOutput(Category.MappingId).println(currIdStr + "\t"+idx);
			ReadData data = this.readArr[idx];
			if(null == data){ // only needed once (first entry of paired read
				ReadDataImpl rData = new ReadDataImpl(idx);
				rData.setFilePosition(
						getGlobFileAccess().translatePosition(bean.getInfo().getFile(), bean.getFilePosition()));
				rData.setLength(SequenceUtil.clearDNA(bean.getSequence()).length());
				this.readArr[idx] = rData;
			}
		} else {
			PrintWriter output = null;
			if(bean.getInfo().getReadType().equals(ReadType.ShortPaired)){
				output = getOutput(Category.UnmappedPaired);
			} else if(bean.getInfo().getReadType().equals(ReadType.Short)) {
				output = getOutput(Category.UnmappedRead);
			} else {
				throw new NotImplementedException("Not implemented for " + bean.getInfo());
			}
			bean.setHeader(StringUtils.EMPTY);
			FileType.fasta.getWriter().write(output, bean);
		}
	}

	private String prevIdx = StringUtils.EMPTY;
	private Integer prevId = -1;
	private Integer getIdx(String currIdStr) {
		Integer id = null;
		if( ! StringUtils.equals(prevIdx, currIdStr)){
			id = getIdentifier(currIdStr);
		} else {
			id = prevId;
		}
		prevIdx = currIdStr;
		prevId = id;
		return id;
	}
	
	private Integer getIdentifier(String id) {
		return this.dataAccess.getReadIdentifier(id);
	}

	private class DataMapper implements EntryMapper{
		
		@Override
		public void mapEntry(EntryMapperBean bean) {
			addEntry(bean);					
		}
	}
	
	private Log getLog() {
		return log;
	}
}
